
%{

/*
 * $Id: html.y,v 1.27 1995/03/29 20:26:53 chris Exp $
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "atom.h"
#include "dtd.h"
#include "html.h"
#include "htmlloc.h"
#include "entity.h"

/*#define YYDEBUG 1 */
/*#define HTMLDEBUG */

/*
 * current->top is the block at the top of the document.
 * curtop represents the current level in the tree
 * which is being filled in with other blocks.
 *
 * lastbegin and lastend are used to keep track of that last tag
 * that we have seen.  If we notice white space between tags,
 * we want to keep track of it, but without these variables, we
 * would have to search for the last one we saw.  Note that only
 * one of these can be set at a time.  The other must be NULL.
 */

struct context *current;

static void GetValidParent(Block *block);
static int _DefaultHTMLError(struct error *errp);
extern int yygetlineno();
static char *_CollapseWhiteSpace(char *original);
static char *ConcatStrs(char *string1, char *string2);

static struct error globalerr;

char *_NoMem = "Out of memory";

atom atomPRE;			/* Cache atoms for speed */
atom atomHTML;
atom atomHEAD;
atom atomTITLE;
atom atomBODY;

HTMLErrorHandler _errorHandler = (HTMLErrorHandler)_DefaultHTMLError;

%}

%union {
    atom atomVal;		/* tag atom */
    char *string;		/* String value */
    int lineno;			/* line number */
    Attribute *attr;
    Block *block;
}

%token ATTREQUAL STARTTAG ENDTAG CLOSEGT ENDCOM DTSTART DTEND
%token ATTRSQUOTE ATTRDQUOTE SHORTTAG
%token <string> ATTRSTR ELEMENT STRING COMMENT DTBODY
%token <string> ENTITY
%token <lineno> STARTCOM

%type <attr> attr attrlist
%type <block> begin_tag any
%type <atomVal> end_tag
%type <string> comment combody doctype text

%%

doc		: any
		| doc any
		| any error
		    {
			_errorHandler(&globalerr);
			/*
			abort();
			*/
		    }
		| doc error
		    {
			_errorHandler(&globalerr);
			/*
			abort();
			*/
		    }
		| error
		    {
			/*
			 * Do nothing.  We will build an empty
			 * document in HTMLParseEnd().
			 */
		    }
		;

any		: text
		    {
			Block *chartext;

			/*
			 * If we get only \n's here, we want to ignore
			 * ignore this test, and mark the last tag or tag
			 * end seen as having been ended by a newline.
			 * We don't want this for <PRE> text, so we'll
			 * ignore that case. What this avoids is one or
			 * more newlines following a markup being confused
			 * for text when they actually were there for
			 * readability.
			 */
			if (current->inPre == 0) {
			    char *cp = $1;
			    char prettify = 0;

			    while (*cp == '\n' || *cp == '\t' || *cp == ' ' ||
								*cp == '\r') {
				if (*cp == '\n' || *cp == '\r')
				    prettify++;
				cp++;
			    }
			    if (*cp == '\0') {	/* Text is whitespace */
				if (prettify) {
				    if (current->lastbegin)
					HTMLAddBFlags(current->lastbegin,
						      PRETTYSTART);
				    else if (current->lastend)
					HTMLAddBFlags(current->lastend,
						      PRETTYEND);
				}
				free($1);	/* Toss it now */
				goto out3;
			    }
			}

			/* Build PCDATA block */
			chartext = HTMLNewBlock(PCDATA, $1, yygetlineno());
			/*
			 * Under normal circumstances we will massage
			 * the input line to turn all \n, \l, \t and space
			 * characters into a single space to represent
			 * white space.  However, we won't touch <PRE>
			 * enclosed text.
			 */
			if (current->inPre)
			    HTMLSetBData(chartext, $1);
			else {
			    /*
			     * If the text begins with a newline
			     * or \r, mark the previous block
			     */
			    char end, start = *($1);
			    
			    if (start == '\n' || start == '\r') {
				if (current->lastbegin)
				    HTMLAddBFlags(current->lastbegin,
						  PRETTYSTART);
				else if (current->lastend)
				    HTMLAddBFlags(current->lastend,
						  PRETTYEND);
			    }
			    end = ($1)[strlen($1) - 1];
			    if (end == '\n' || end == '\r')
				HTMLAddBFlags(chartext, PRETTYSTART);

			    HTMLSetBData(chartext, _CollapseWhiteSpace($1));
			}
			/* Set up appropriate parent */
			GetValidParent(chartext);
			/* Insert into current->curtop */
			_insertBlock(current->curtop, chartext);
out3:			;
		    }
		| doctype
		    {
			/* Modify the default current->doctype */
			free(HTMLGetBData(current->doctype));
			HTMLSetBData(current->doctype, $1);
			HTMLSetBLineno(current->doctype, yygetlineno());
		    }
		| comment
		    {
			Block *chartext;

			/* Build COMMENT block */
			chartext = HTMLNewBlock(CMNT, $1, yygetlineno());
			/* Insert into current->curtop */
			_insertBlock(current->curtop, chartext);
		    }
		| begin_tag
		    {
			/*
			 * First we'll check to see if it's in the
			 * list of our pre-built blocks.  If so,
			 * we'll transfer to content to the predeclared
			 * block and free the existing one.
			 * If the pre-built block hasn't been inserted
			 * yet (by a optional check, below), insert it now.
			 * Also check for a <PRE> and increment counter so
			 * we don't munge text inside it.
			 */
			if (HTMLGetBAtom($1) == atomHTML) {
			    if (HTMLGetBParent(current->html) == 0) {
				HTMLSetBLineno(current->html,
					       HTMLGetBLineno($1));
				current->curtop = current->html;
				HTMLDeleteBFlags(current->curtop, OPTTAG);
				_insertBlock(current->top, current->html);
				current->lastbegin = current->html;
				current->lastend = 0;
			    }
			    else {
				struct error err;
				char buf[MAX_ERROR_LEN];

				err.lineno = HTMLGetBLineno($1);
				err.severity = E_SEVERE;
				err.text = HTMLGetBData($1);
				err.type = E_EXTRATAG;
				err.message = buf;
				sprintf(buf,
					"<%s> has appeared more than"
					" once in the document.\n"
					"Ignoring all but first instance.",
					HTMLGetBData($1));
				_errorHandler(&err);
			    }
			    HTMLFreeBlock($1);
			}
			else if (HTMLGetBAtom($1) == atomHEAD) {
			    if (HTMLGetBParent(current->head) == 0) {
				HTMLSetBLineno(current->head,
					       HTMLGetBLineno($1));
				current->curtop = current->head;
				HTMLDeleteBFlags(current->curtop, OPTTAG);
				if (HTMLGetBParent(current->html) == 0) {
				    /* Insert current->html, if needed */
				    HTMLAddBFlags(current->html,
						  PRETTYSTART | PRETTYEND);
				    _insertBlock(current->top,
						current->html);
				}
				_insertBlock(current->html, current->head);
				current->lastbegin = current->head;
				current->lastend = 0;
			    }
			    else {
				struct error err;
				char buf[MAX_ERROR_LEN];

				err.lineno = HTMLGetBLineno($1);
				err.severity = E_SEVERE;
				err.text = HTMLGetBData($1);
				err.type = E_EXTRATAG;
				err.message = buf;
				sprintf(buf,
					"<%s> has appeared more than"
					" once in the document.\n"
					"Ignoring all but first instance.",
					HTMLGetBData($1));
				_errorHandler(&err);
			    }
			    HTMLFreeBlock($1);
			}
			else if (HTMLGetBAtom($1) == atomTITLE) {
			    if (HTMLGetBParent(current->title) == 0) {
				HTMLSetBLineno(current->title,
					       HTMLGetBLineno($1));
				current->curtop = current->title;
				HTMLDeleteBFlags(current->curtop, OPTTAG);
				/* Insert current->html, if needed */
				if (HTMLGetBParent(current->html) == 0) {
				    HTMLAddBFlags(current->html,
						  PRETTYSTART | PRETTYEND);
				    _insertBlock(current->top,
						current->html);
				}
				/* Insert current->head, if needed */
				if (HTMLGetBParent(current->head) == 0) {
				    HTMLAddBFlags(current->head,
						  PRETTYSTART | PRETTYEND);
				    _insertBlock(current->html,
						current->head);
				}
				_insertBlock(current->head, current->title);
				current->lastbegin = current->title;
				current->lastend = 0;
			    }
			    else {
				struct error err;
				char buf[MAX_ERROR_LEN];

				err.lineno = HTMLGetBLineno($1);
				err.severity = E_SEVERE;
				err.text = HTMLGetBData($1);
				err.type = E_EXTRATAG;
				err.message = buf;
				sprintf(buf,
					"<%s> has appeared more than"
					" once in the document.\n"
					"Ignoring all but first instance.",
					HTMLGetBData($1));
				_errorHandler(&err);
			    }
			    HTMLFreeBlock($1);
			}
			else if (HTMLGetBAtom($1) == atomBODY) {
			    if (HTMLGetBParent(current->body) == 0) {
				HTMLSetBLineno(current->body,
					       HTMLGetBLineno($1));
				current->curtop = current->body;
				HTMLDeleteBFlags(current->curtop, OPTTAG);
				/* Insert current->html, if needed */
				if (HTMLGetBParent(current->html) == 0) {
				    HTMLAddBFlags(current->html,
						  PRETTYSTART | PRETTYEND);
				    _insertBlock(current->top, current->html);
				}
				_insertBlock(current->html, current->body);
				current->lastbegin = current->body;
				current->lastend = 0;
			    }
			    else {
				struct error err;
				char buf[MAX_ERROR_LEN];

				err.lineno = HTMLGetBLineno($1);
				err.severity = E_SEVERE;
				err.text = HTMLGetBData($1);
				err.type = E_EXTRATAG;
				err.message = buf;
				sprintf(buf,
					"<%s> has appeared more than"
					" once in the document.\n"
					"Ignoring all but first instance.",
					HTMLGetBData($1));
				_errorHandler(&err);
			    }
			    HTMLFreeBlock($1);
			}
			else {
			    if (HTMLGetBAtom($1) == atomPRE) 
				current->inPre++;
			    /* Set up appropriate parent */
			    GetValidParent($1);
			    /* Insert block */
			    _insertBlock(current->curtop, $1);
			    /*
			     * shift down one level only if this should have a
			     * mandatory or optional terminator.
			     * tags like HR don't have terminators and won't
			     * cause a shift.
			     */
			    if (IsEleNoneTermed(HTMLGetBAtom($1)) == 0)
				current->curtop = $1;
			    current->lastbegin = $1;
			    current->lastend = 0;
			}
		    }
		| end_tag
		    {
			Block *save = current->curtop;

			if (current->curtop == current->top) {
			    struct error err;
			    char buf[MAX_ERROR_LEN];

			    err.lineno = yygetlineno();
			    err.severity = E_CRASH;
			    err.text = GetAtomVal($1);
			    err.type = E_NOOPEN;
			    err.message = buf;
			    sprintf(buf,
				    "</%s> has no matching open tag",
				    GetAtomVal($1));
			    _errorHandler(&err);
			    /*
			     * Ignore this end tag and continue processing.
			     */
			    goto out2;
			}

			/*
			 * Deal with shorttag in input
			 */
			if ($1 == -1) {
			    HTMLAddBFlags(current->curtop, SHRTTAG);
			    current->lastbegin = 0;
			    current->lastend = current->curtop;
			    if (HTMLGetBAtom(current->curtop) == atomPRE) {
				if (--(current->inPre) < 0) {
				    current->inPre = 0;	/*XXX*/
				    fprintf(stderr, "lost <PRE>\n");
				}
			    }
			    current->curtop = HTMLGetBParent(current->curtop);
			    goto out2;
			}
			/*
			 * We need to move up the tree trying to
			 * find the block this matches.  If we find
			 * one with E_TERM_MAND and it doesn't match
			 * this end tag, we'll report an error.
			 * If we reach current->op without finding a match
			 * we'll report an unmatched terminator.
			 */
			while ($1 != HTMLGetBAtom(current->curtop)) {
			    if (IsEleOptTermed(HTMLGetBAtom(current->curtop))) {
				HTMLAddBFlags(current->curtop, OPTCLOSE);
				current->curtop =
						HTMLGetBParent(current->curtop);
			    }
			    else if (IsEleMandTermed(HTMLGetBAtom(current->curtop))) {
				struct error err;
				char buf[MAX_ERROR_LEN];

				err.lineno = HTMLGetBLineno(current->curtop);
				err.severity = E_SEVERE;
				err.text = HTMLGetBData(current->curtop);
				err.type = E_NOCLOSE;
				err.message = buf;
				sprintf(buf,
					"<%s> missing required end tag",
					HTMLGetBData(current->curtop));
				_errorHandler(&err);
				if (HTMLGetBAtom(current->curtop) == atomPRE)
				    if (--(current->inPre) < 0) {
					current->inPre = 0;	/*XXX*/
					fprintf(stderr, "lost <PRE>\n");
				    }
				current->curtop =
						HTMLGetBParent(current->curtop);
			    }
			    if (current->curtop == current->top) {
				struct error err;
				char buf[MAX_ERROR_LEN];

				err.lineno = yygetlineno();
				err.severity = E_CRASH;
				err.text = GetAtomVal($1);
				err.type = E_NOOPEN;
				err.message = buf;
				sprintf(buf,
					"</%s> has no matching open tag",
					GetAtomVal($1));
				_errorHandler(&err);
				/*
				 * Ignore this end tag and continue processing.
				 */
				current->curtop = save;	/* restore position */
				goto out2;
			    }
			}
			/* end the current matching block */
			HTMLDeleteBFlags(current->curtop, OPTCLOSE);
			if (HTMLGetBAtom(current->curtop) == atomPRE)
			    if (--(current->inPre) < 0) {
				current->inPre = 0;	/*XXX*/
				fprintf(stderr, "lost <PRE>\n");
			    }
			current->lastbegin = 0;
			current->lastend = current->curtop;
			current->curtop = HTMLGetBParent(current->curtop);
out2:			;
		    }
		;


text		: STRING
		    {
			$$ = $1;
		    }
		| ENTITY
		    {
			int i;

			$$ = malloc(2);
			if ($$ == NULL) {
			    struct error err;

			    err.lineno = yygetlineno();
			    err.severity = E_CRASH;
			    err.text = $1;
			    err.type = E_NOMEM;
			    err.message = _NoMem;
			    _errorHandler(&err);
			    exit(2);
			}

			i = GetEntityValue($1);
			if (i == -1) {		/* Error */
			    struct error err;
			    char buf[MAX_ERROR_LEN];

			    err.lineno = yygetlineno();
			    err.severity = E_SEVERE;
			    err.text = $1;
			    err.type = E_UNKNOWNENTITY;
			    err.message = buf;
			    sprintf(buf,
				    "%s is not a recognized entity",
				    $1);
			    _errorHandler(&err);
			    $$ = $1;
			}
			else {
			    $$[0] = i;
			    $$[1] = '\0';
			    free($1);
			}
		    }
		| text STRING 
		    {
			$$ = ConcatStrs($1, $2);
			free($1);
			free($2);
		    }
		| text ENTITY 
		    {
			char buf[MAX_ERROR_LEN];
			int i = GetEntityValue($2);

			if (i == -1) {		/* Error */
			    struct error err;

			    err.lineno = yygetlineno();
			    err.severity = E_SEVERE;
			    err.text = $2;
			    err.type = E_UNKNOWNENTITY;
			    err.message = buf;
			    sprintf(buf,
				    "%s is not a recognized entity",
				    $2);
			    _errorHandler(&err);
			    $$ = ConcatStrs($1, $2);
			    free($1);
			    free($2);
			}
			else {
			    buf[0] = i;
			    buf[1] = '\0';
			    $$ = ConcatStrs($1, buf);
			    free($1);
			    free($2);
			}
		    }
		;

begin_tag	: STARTTAG ELEMENT CLOSEGT
		    {
			/* Build tag block */
			$$ = HTMLNewBlock(TAG, $2, yygetlineno());
			/* We need to be sure no required attrs are missing */
			verifyAttrs($$);
		    }
		| STARTTAG ELEMENT attrlist CLOSEGT
		    {
			/* Build tag block */
			$$ = HTMLNewBlock(TAG, $2, yygetlineno());
			$$->attrs = $3;
			verifyAttrs($$);
		    }
		;

end_tag		: ENDTAG ELEMENT CLOSEGT
		    {
			$$ = GetAtom($2);
			free($2);
		    }
		| ENDTAG ELEMENT attrlist CLOSEGT
		    {
			$$ = GetAtom($2);
			free($2);
		    }
		| SHORTTAG
		    {
			$$ = -1;	/* Shorttags match last open tag */
		    }
		;

attrlist	: attr
		    {
			$$ = $1;
		    }
		| attrlist attr
		    {
			Attribute *ap = $1;

			while (ap->next)
			    ap = ap->next;
			ap->next = $2;
			$$ = $1;
		    }
		| attrlist error
		    {
			struct error err;
			char buf[MAX_ERROR_LEN];

			err.lineno = yygetlineno();
			err.severity = E_SEVERE;
			err.text = "";
			err.type = E_ATTR;
			err.message = buf;
			sprintf(buf, "malformed attribute");
			_errorHandler(&err);
			$$ = $1;
		    }
		;

attr		: ATTRSTR ATTREQUAL ATTRSTR
		    {
			/* Build attribute with text value */
			$$ = HTMLNewAttr($1, $3, yygetlineno());
		    }
		| ATTRSTR ATTREQUAL ATTRSQUOTE text ATTRSQUOTE
		    {
			/* Build attribute with text value */
			$$ = HTMLNewAttr($1, $4, yygetlineno());
			$$->flags |= SQUOTEDVALUE;
		    }
		;
		| ATTRSTR ATTREQUAL ATTRDQUOTE text ATTRDQUOTE
		    {
			/* Build attribute with text value */
			$$ = HTMLNewAttr($1, $4, yygetlineno());
			$$->flags |= DQUOTEDVALUE;
		    }
		;
		| ATTRSTR ATTREQUAL ATTRSQUOTE ATTRSQUOTE
		    {
			/* Build attribute with empty value */
			char *str = (char *)calloc(1, 1); /* Null string */

			$$ = HTMLNewAttr($1, str, yygetlineno());
			$$->flags |= SQUOTEDVALUE;
		    }
		;
		| ATTRSTR ATTREQUAL ATTRDQUOTE ATTRDQUOTE
		    {
			/* Build attribute with empty value */
			char *str = (char *)calloc(1, 1); /* Null string */

			$$ = HTMLNewAttr($1, str, yygetlineno());
			$$->flags |= DQUOTEDVALUE;
		    }
		;
		| ATTRSTR
		    {
			/* Build attribute with no value */
			$$ = HTMLNewAttr($1, 0, yygetlineno());
		    }
		;

comment		: STARTCOM combody ENDCOM
		    {
			$$ = $2;
		    }
		| STARTCOM ENDCOM
		    {
			$$ = "";
		    }
		| STARTCOM combody error
		    {
			struct error err;
			char buf[MAX_ERROR_LEN];

			err.lineno = $1;
			err.severity = E_CRASH;
			err.text = $2;
			err.type = E_COMMENTTERM;
			err.message = buf;
			sprintf(buf,
				"An unterminated comment has been"
				" found in the document.\n");
			_errorHandler(&err);
			/* XXX exit here */
			$$ = "";
		    }
		;
combody		: COMMENT
		    {
			$$ = $1;
		    }
		| combody COMMENT
		    {
			/* Merge the two strings */
			$$ = ConcatStrs($1, $2);
			free($1);
			free($2);
		    }
		;

doctype		: DTSTART DTBODY DTEND
		    {
			$$ = $2;
		    }
		;

%%

static int
_DefaultHTMLError(struct error *errp)
{
    char *type;

    if (errp->severity > E_WARNING)
	type = "Error";
    else
	type = "Warning";
    
    fprintf(stderr,
	    "%s: %d: %s\n",
	    type,
	    errp->lineno,
	    errp->message);

    return (1);
}
/*
 * We predefine a small group of structures in the tree: HTML,
 * HEAD, TITLE, and BODY.  TITLE is required, and its existance
 * forces the HEAD and BODY, even though their tags are optional.
 * BODY is forced by almost any document content.  We have decided
 * to add these to the tree even if they are not in the document.
 *
 * Using this both solves a problem and creates one.  The solved
 * problem is how we handle these types where the start tag is
 * optional.  The created problem is that we will have to add
 * special handling for things that might drop us into one of these
 * cases.
 */
struct context *
_initTree()
{
    struct context *con = (struct context *)calloc(sizeof(struct context), 1);
/* NPM: extern char *strdup(const char *); */	/* Required on SGI */

    /* Allocate context data structures */
    if (con == NULL)
	goto err;
    con->top = (Block *)calloc(sizeof(Block), 1);
    if (con->top == NULL)
	goto err;
    con->html = (Block *)calloc(sizeof(Block), 1);
    if (con->html == NULL)
	goto err;
    con->head = (Block *)calloc(sizeof(Block), 1);
    if (con->head == NULL)
	goto err;
    con->title = (Block *)calloc(sizeof(Block), 1);
    if (con->title == NULL)
	goto err;
    con->body = (Block *)calloc(sizeof(Block), 1);
    if (con->body == NULL)
	goto err;
    con->doctype = (Block *)calloc(sizeof(Block), 1);
    if (con->doctype == NULL)
	goto err;

    /* Stuff some useful info into top block */
    con->top->data = strdup("document top");
    con->top->lineno = -1;

    /* Fill in HTML block */
    con->html->type = TAG;
    con->html->data = strdup("HTML");
    con->html->bAtom = atomHTML;
    con->html->flags = OPTTAG;
    con->html->lineno = -1;

    /* Fill in HEAD block */
    con->head->type = TAG;
    con->head->data = strdup("HEAD");
    con->head->bAtom = atomHEAD;
    con->html->flags = OPTTAG;
    con->head->lineno = -1;

    /* Fill in TITLE block */
    con->title->type = TAG;
    con->title->data = strdup("TITLE");
    con->title->bAtom = atomTITLE;
    con->title->lineno = -1;

    /* Fill in BODY block */
    con->body->type = TAG;
    con->body->data = strdup("BODY");
    con->body->bAtom = atomBODY;
    con->body->flags = OPTTAG;
    con->body->lineno = -1;

    /*
     * Make sure a doctype is in place.
     * We are going to force the doctype to be the first thing
     * in the tree, regardless of it's actual position in the document.
     * If fact, we put it in even if it doesn't exist.
     * Even comments will not be allowed before it.  (They will be
     * moved back).  This may be a mistake XXX
     */

    /* Fill in doctype block */
    con->doctype->type = DOCTYPE;
    con->doctype->data = strdup("HTML PUBLIC \"-//W3O//DTD W3 HTML 2.0//EN\"");
    con->doctype->lineno = -1;
    con->doctype->parent = con->top;
    con->doctype->flags = OPTTAG;
    con->top->children = con->doctype;
    con->curtop = con->top;
    return (con);
err:
    {
	struct error err;

	err.lineno = -1;
	err.severity = E_CRASH;
	err.text = NULL;
	err.type = E_NOMEM;
	err.message = _NoMem;
	_errorHandler(&err);
	exit(2);
    }
}

/*
 * Check the validity of the attribute list.
 */
static void
verifyAttrs(Block *bp)
{
    Attribute *ap = HTMLGetBAttrs(bp);
    atom bAtom = HTMLGetBAtom(bp);	/* element atom */
    atom *reqAtoms = GetElementReqAttrs(bAtom);
    int i;
    int valid;

    if (GetElementInfo(bAtom) == 0)
	return;			/* Can't do attrs for unknown elements */
    /* Make sure all attributes in list are valid */
    while (ap) {
	if (ValidAttribute(bAtom, HTMLGetAAtom(ap)) == 0) {
	    struct error err;
	    char buf[MAX_ERROR_LEN];
	     
	    err.lineno = HTMLGetBLineno(bp);
	    err.severity = E_SEVERE;
	    err.text = HTMLGetAData(ap);
	    err.type = E_INVALIDEATTR;
	    err.message = buf;
	    sprintf(buf,
		    "Attribute '%s' not allowed in markup <%s>",
		    HTMLGetAName(ap),
		    HTMLGetBData(bp));
	    _errorHandler(&err);
	}
	ap = ap->next;
    }

    /* Search list for each required attribute */
    if (reqAtoms) {			/* Unknown elements will not work */
	for (i = 0; (i < 10) && reqAtoms[i]; i++) {
	    ap = HTMLGetBAttrs(bp);
	    valid = 0;
	    /* Loop through the attributes looking for this required one */
	    while (ap) {
		if (reqAtoms[i] == HTMLGetAAtom(ap)) {
		    valid = 1;
		    break;
		}
		ap = ap->next;
	    }
	    if (valid == 0) {
		struct error err;
		char buf[MAX_ERROR_LEN];
		 
		err.lineno = HTMLGetBLineno(bp);
		err.severity = E_SEVERE;
		err.text = HTMLGetBData(bp);
		err.type = E_MISSINGATTR;
		err.message = buf;
		sprintf(buf,
			"Required attribute '%s' missing in markup <%s>",
			GetAtomVal(reqAtoms[i]),
			HTMLGetBData(bp));
		_errorHandler(&err);
	    }
	}
    }
}

void
_insertBlock(Block *parent, Block *child)
{
    Block *next;

    if (HTMLGetBChild(parent) == NULL) {
	HTMLSetBChild(parent, child);
    }
    else {
	next = HTMLGetBChild(parent);
	while (HTMLGetBSib(next))
	    next = HTMLGetBSib(next);
	HTMLSetBSib(next, child);
    }
    /* Remember parent */
    HTMLSetBParent(child, parent);
}

/*
 * Create a new string from two strings passed in.
 * Caller must handle all freeing of memory.
 */
static char *
ConcatStrs(char *string1, char *string2)
{
    char *newstr = malloc(strlen(string1) + strlen(string2) + 1);
    if (newstr == NULL) {
	struct error err;

	err.lineno = yygetlineno();
	err.severity = E_CRASH;
	err.text = string1;
	err.type = E_NOMEM;
	err.message = _NoMem;
	_errorHandler(&err);
	exit(2);
    }
    strcpy(newstr, string1);
    strcat(newstr, string2);
    return (newstr);
}

yyerror(char *s)
{
    extern char *yytext;

    globalerr.lineno = yygetlineno();
    globalerr.severity = E_CRASH;
    globalerr.text = yytext;
    globalerr.type = E_SYNTAX;
    globalerr.message = "Syntax error: Cannot parse document";
    /*
     * Error handler is now being called when the error token is
     * found in the yacc grammar.  In some cases the error will
     * be ignored.
     * The only reason we use this global function is because
     * yacc always calls this function on syntax errors.
     
    _errorHandler(&err);
     */
}

/*
 * Try to find an appropriate place in the current
 * tree for the block.  The cases used here are fairly simple.
 * If the block is a valid child of curtop, we will return curtop.
 * If not, we will attempt to close elements with optional terminators
 * until we find an element that block can be a child of.
 *
 * We will also create the boilerplate needed to handle totally
 * optional elements (HEAD, BODY, and HTML).
 *
 * We potentially have two serious errors to deal with in the search.
 * First, there may be no element that can contain the block.  In this
 * case we report the error then return curtop as the container
 * (even though syntactically that is incorrect).
 * The other error would happen if while moving up the tree we
 * find an element tag that is requires a manditory end tag, yet
 * cannot contain the block.  In this case we print an error, then
 * cram block into the original element (even though it is incorrect).
 */
void
GetValidParent(Block *block)
{
    /*
     * If this tag is allowed in the content of
     * type curtop, we will add it to the child
     * list.
     *
     * If it is not, but has an optional close,
     * we will pretend that we have seen a close
     * for the tag in curtop.
     * If it requires a mandatory close, we will
     * print an error message about the tag being
     * an inappropriate content (or child).
     */
    if (ValidRelationship(HTMLGetBAtom(current->curtop), HTMLGetBAtom(block)) == 0) {
	char buf[MAX_ERROR_LEN];

	/* Check for implied HEAD */
	if (ValidRelationship(atomHEAD, HTMLGetBAtom(block))) {
	    struct error err;

	    err.lineno = HTMLGetBLineno(block);
	    err.severity = E_INFORMATION;
	    err.text = HTMLGetBData(block);
	    err.message = buf;

	    /* Link current->html first, if needed */
	    if (HTMLGetBParent(current->html) == 0) {
		err.type = E_MISSINGHTML;
		if (HTMLGetBType(block) == PCDATA)
		    sprintf(buf,
			"Character text \"%s\" implies optional <HTML>",
			HTMLGetBData(block));
		else
		    sprintf(buf,
			    "<%s> implies optional <HTML>",
			    HTMLGetBData(block));
		_errorHandler(&err);
		HTMLAddBFlags(current->html, PRETTYSTART | PRETTYEND);
		_insertBlock(current->top, current->html);
	    }
	    /* Then link current->head if needed */
	    if (HTMLGetBParent(current->head) == 0) {
		err.type = E_MISSINGHEAD;
		if (HTMLGetBType(block) == PCDATA)
		    sprintf(buf,
			"Character text \"%s\" implies optional <HEAD>",
			HTMLGetBData(block));
		else
		    sprintf(buf,
			    "<%s> implies optional <HEAD>",
			    HTMLGetBData(block));
		_errorHandler(&err);
		HTMLAddBFlags(current->head, PRETTYSTART | PRETTYEND);
		_insertBlock(current->html, current->head);
	    }
	    /* Should we attach title here? */
	    current->curtop = current->head;
	}
	/* Check for implied BODY */
	else if ((current->body->children == 0) &&
			ValidRelationship(atomBODY, HTMLGetBAtom(block))) {
	    struct error err;

	    err.lineno = HTMLGetBLineno(block);
	    err.severity = E_INFORMATION;
	    err.text = HTMLGetBData(block);
	    err.message = buf;

	    /* Link current->html first, if needed */
	    if (HTMLGetBParent(current->html) == 0) {
		err.type = E_MISSINGHTML;
		if (HTMLGetBType(block) == PCDATA)
		    sprintf(buf,
			"Character text \"%s\" implies optional <HTML>",
			HTMLGetBData(block));
		else
		    sprintf(buf,
			    "<%s> implies optional <HTML>",
			    HTMLGetBData(block));
		_errorHandler(&err);
		HTMLAddBFlags(current->html, PRETTYSTART | PRETTYEND);
		_insertBlock(current->top, current->html);
	    }
	    /* Then link current->head if needed */
	    if (HTMLGetBParent(current->head) == 0) {
		err.type = E_MISSINGHEAD;
		if (HTMLGetBType(block) == PCDATA)
		    sprintf(buf,
			"Character text \"%s\" implies optional <HEAD>",
			HTMLGetBData(block));
		else
		    sprintf(buf,
			    "<%s> implies optional <HEAD>",
			    HTMLGetBData(block));
		_errorHandler(&err);
		HTMLAddBFlags(current->head, PRETTYSTART | PRETTYEND);
		_insertBlock(current->html, current->head);
	    }
	    /* Then link current->body if needed */
	    if (HTMLGetBParent(current->body) == 0) {
		err.type = E_MISSINGBODY;
		if (HTMLGetBType(block) == PCDATA)
		    sprintf(buf,
			"Character text \"%s\" implies optional <BODY>",
			HTMLGetBData(block));
		else
		    sprintf(buf,
			    "<%s> implies optional <BODY>",
			    HTMLGetBData(block));
		_errorHandler(&err);
		HTMLAddBFlags(current->body, PRETTYSTART | PRETTYEND);
		_insertBlock(current->html, current->body);
	    }
	    current->curtop = current->body;
	}
	else if ((current->curtop != current->top) && 
		    IsEleMandTermed(HTMLGetBAtom(current->curtop))) {
	    /*
	     * We have found an element that is not
	     * permitted in the current parent element.
	     * Unfortunately, the current parent requires
	     * a terminating string like </foo> but none
	     * has been seen.  Thus we have an error.
	     *
	     * We are hoping that the error here is
	     * the use of an inappropriate content
	     * tag rather than a lack of the correct
	     * terminator.  Thus, we will issue a
	     * warning, but permit the block to remain
	     * at this inappropriate (according to the
	     * spec) place in the structure.
	     */
	    struct error err;

	    err.lineno = HTMLGetBLineno(block);
	    err.severity = E_SEVERE;
	    err.text = HTMLGetBData(block);
	    err.type = E_CANTCLOSE;
	    err.message = buf;
	    if (HTMLGetBType(block) == PCDATA)
		sprintf(buf,
			"Character text \"%s\""
			" not allowed in context of <%s>",
			HTMLGetBData(block),
			HTMLGetBData(current->curtop));
	    else
		sprintf(buf,
			"<%s>"
			" not allowed in context of <%s>",
			HTMLGetBData(block),
			HTMLGetBData(current->curtop));
	    _errorHandler(&err);

	    /*
	     * Now allow the block to be inserted as a
	     * child, anyway.
	     */
	}
	else {
	    Block *save = current->curtop;
	    /*
	     * Assume E_TERM_OPT. E_TERM_NONE couldn't get here
	     * Go up the list closing optional elements
	     * until we find a parent it can hang off of.
	     */
	    do {

#ifdef HTMLDEBUG
	fprintf(stderr,
		"<%s> is not appropriate child of <%s>\n",
		HTMLGetBData(block),
		HTMLGetBData(current->curtop));
#endif /* HTMLDEBUG */

		if (current->curtop == current->top)
		    break;
		if (IsEleMandTermed(HTMLGetBAtom(current->curtop))) {
		    /*
		     * In moving up the tree, we have found
		     * an element with a manditory close.
		     * Since our current element cannot
		     * force this to close, we have an
		     * error.  Report the error and
		     * allow the current element to be
		     * shoved into the tree at its current
		     * location
		     */
		    current->curtop = current->top; /* Forces error */
		    break;
		}

#ifdef HTMLDEBUG
		else {
		    fprintf(stderr,
			    "<%s> forcing optional close of <%s>\n",
			    HTMLGetBData(block),
			    HTMLGetBData(current->curtop));
		}
#endif /* HTMLDEBUG */

		/*
		 * A small problem here.  Although we are marking this
		 * tag as having an optional close, we may discover an
		 * error later in this loop, leading to this not being
		 * closed.  To fix this, we'll turn of the flag later if
		 * we find a valid closing tag. XXX
		 */
		HTMLAddBFlags(current->curtop, OPTCLOSE);
		current->curtop = HTMLGetBParent(current->curtop);
		if (current->curtop == current->top)
		    break;
	    } while (ValidRelationship(HTMLGetBAtom(current->curtop),
						    HTMLGetBAtom(block)) == 0);
	    /*
	     * We have an interesting case here.
	     * If current->curtop is the same as current->top, that means
	     * we have a block that is illegal in the
	     * structure and doesn't force an optional
	     * tag to close.  We will print a warning
	     * message then shove it in at the
	     * inappropriate place.
	     */
	    if (current->curtop == current->top) {
		struct error err;

		current->curtop = save;
		err.lineno = HTMLGetBLineno(block);
		err.severity = E_SEVERE;
		err.text = HTMLGetBData(block);
		err.type = E_BADPARENT;
		err.message = buf;
		if (current->curtop == current->top) {
		    if (HTMLGetBType(block) == PCDATA)
			sprintf(buf,
				"Character text \"%s\""
		    		" not allowed at top level of html document",
				HTMLGetBData(block));
		    else
			sprintf(buf,
				"<%s>",
		    		" not allowed at top level of html document",
				HTMLGetBData(block));
		}
		else {
		    if (HTMLGetBType(block) == PCDATA)
			/*
			 * Limit the amount of text printed out in
			 * error messages.
			 */
			if (strlen(HTMLGetBData(block)) > 40)
			    sprintf(buf,
				    "Character text \"%40.40s ... \""
				    " not allowed in context of <%s>",
				    HTMLGetBData(block),
				    HTMLGetBData(current->curtop));
			else
			    sprintf(buf,
				    "Character text \"%s\""
				    " not allowed in context of <%s>",
				    HTMLGetBData(block),
				    HTMLGetBData(current->curtop));

		    else
			sprintf(buf,
				"<%s>"
				" not allowed in context of <%s>",
				HTMLGetBData(block),
				HTMLGetBData(current->curtop));
		}
		_errorHandler(&err);
	    }
	}
    }
    /*
     * We don't return anything.  All we do is adjust the global
     * variable current->curtop.
     */
}

static char *
_CollapseWhiteSpace(char *original)
{
    char *newString = malloc(strlen(original) + 1);
    char *cp, *cp1;
    int inWhite = 0;

    if (newString == NULL) {
	struct error err;

	err.lineno = yygetlineno();
	err.severity = E_CRASH;
	err.text = original;
	err.type = E_NOMEM;
	err.message = _NoMem;
	_errorHandler(&err);
	exit(2);
    }
    cp = original;
    cp1 = newString;
    while (*cp) {

	switch (*cp) {
	    default:
		*cp1++ = *cp;
		inWhite = 0;
		break;
	    case '\t':
	    case '\n':
	    case '\r':
	    case ' ':
		if (inWhite == 0) {
		    *cp1++ = ' ';
		    inWhite = 1;
		}
		break;
	}
	cp++;
    }
    *cp1 = '\0';
    free(original);
    return (newString);
}
